package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.ObjectUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constans.RedisConstans;
import com.tianji.learning.domain.po.PointsRecord;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.enums.PointsRecordType;
import com.tianji.learning.mapper.PointsRecordMapper;
import com.tianji.learning.mq.message.SignInMessage;
import com.tianji.learning.service.SignRecordConService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;

import static com.tianji.common.constants.MqConstants.Exchange.LEARNING_EXCHANGE;
import static com.tianji.common.constants.MqConstants.Key.SIGN_IN;
import static com.tianji.common.utils.DateUtils.DATE_SUFFIX_FORMATTER;
import static com.tianji.learning.constans.RedisConstans.POINTS_BOARD_KEY_PREFIX;


@Slf4j
@Service
@RequiredArgsConstructor
public class SignRecordConServiceImpl extends ServiceImpl<PointsRecordMapper, PointsRecord> implements SignRecordConService {
    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper MqHelper;


    /**
     * 1.签到
     */
    @Override
    public SignResultVO addSignRecords() {
        //1、签到
        //1.1获取用户id
        Long userId = UserContext.getUser();
        //1.2获取签到时间
        LocalDate now = LocalDate.now();
        //1.3拼接key
        String dateStr = now.format(DateUtils.DATE_SUFFIX_FORMATTER);
        String key = RedisConstans.SIGN_RECORD_KEY_PREFIX + dateStr + userId;
        //1.4计算offset
        int offset = now.getDayOfMonth() - 1;
        //1.5保存签到信息,并且校验是否重复签到
        boolean keyExists = false;
        Long existsList = redisTemplate.countExistingKeys(Collections.singleton(key));
        if (existsList > 0) {
            keyExists = true;
        }
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        if (BooleanUtils.isTrue(exists)) {
            throw new BizIllegalException("不允许重复签到");
        }
        //1.6当月第一次签到，为key设置过期时间，（次月开始时间—当前时间）
        if (!keyExists) {
            LocalDateTime beginTime = LocalDateTime.now();
            LocalDateTime endTime = DateUtils.getMonthBeginTime(now.plusMonths(1));
            redisTemplate.expire(key, DateUtils.between(beginTime, endTime));
        }
        // 2、计算连续签到天数
        int days = countSignDays(key, LocalDate.now().getDayOfMonth());

        // 3、计算签到奖励积分
        int rewardPoints = computePoints(days);

        // 4、封装VO
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(days);
        vo.setRewardPoints(rewardPoints);

        //1.6保存积分明细
        try {
            MqHelper.send(LEARNING_EXCHANGE, SIGN_IN, SignInMessage.of(vo.totalPoints(), UserContext.getUser()));
        } catch (Exception e) {
            log.error("积分明细发送失败", e);
        }

        return vo;
    }

    /**
     * 计算连续签到天数奖励积分
     *
     * @param signDays
     * @return
     */
    private int computePoints(int signDays) {
        switch (signDays) {
            case 7:
                return 10;
            case 14:
                return 20;
            case 28:
                return 40;
            default:
                return 0;

        }
    }

    /**
     * 查询连续签到天数
     *
     * @param key
     * @param dayOfMonth
     * @return
     */
    private int countSignDays(String key, int dayOfMonth) {
        //获取从第一天开始的连续签到记录
        List<Long> result = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (ObjectUtils.isEmpty(result)) {
            return 0;
        }
        int num = result.get(0).intValue();
        //定义一个计数器
        int count = 0;
        while ((num & 1) == 1) {
            count++;
            num >>>= 1;
        }

        return count;
    }


    /**
     * 3.接受回答的积分
     *
     * @param userId 用戶id
     * @param points 积分
     * @param type   积分类型
     */
    @Override
    public void addPointRecord(Long userId, int points, PointsRecordType type) {
        // 1、判断当前积分类型是否有积分上限
        int maxPoints = type.getMaxPoints();
        LocalDateTime now = LocalDateTime.now();
        if (maxPoints > 0) {
            // 2、如果有，则判断当天该用户已获取积分总额是否超过每日上限
            // 2.1、查询【当前用户】【今日】通过该【积分类型】已获取【积分总数】
            LocalDateTime begin = DateUtils.getDayStartTime(now);
            LocalDateTime end = DateUtils.getDayEndTime(now);

            PointsRecord record = this.query()
                    .select("sum(points) as points")
                    .eq("user_id", userId)
                    .eq("`type`", type)
                    .between("create_time", begin, end)
                    .one();
            //2.2判断是否超过上限
            int currentDayPoints = ObjectUtils.isEmpty(record) || ObjectUtils.isEmpty(record.getPoints()) ? 0 : record.getPoints();
            // 2.2、判断是否超过上限
            if (currentDayPoints >= maxPoints) {
                // 2.3、超过直接结束
                return;
            }

            // 2.3、处理 已有积分 + 本次待加积分 > 积分上限的情况，不能直接加了，而是要加上：上限 - 已有积分 的差值部分
            if (currentDayPoints + points > maxPoints) {
                points = maxPoints - currentDayPoints;
            }
        }

        // 3、如果没有，直接保存积分记录
        PointsRecord pointsRecord = new PointsRecord().setPoints(points).setUserId(userId).setType(type);
        save(pointsRecord);

        // 4、累加当前用户的积分至redis zset结构
        // 4.1、组装zset排行榜的key（boards:202408）
        String key = POINTS_BOARD_KEY_PREFIX + now.format(DATE_SUFFIX_FORMATTER);

        // 4.2、累加积分
        redisTemplate.opsForZSet().incrementScore(key, userId.toString(), points);
    }

    /**
     * 2.查询签到记录
     *
     * @return
     */
    @Override
    public Byte[] querySignRecords() {
        //1.拼接key
        //1.1获取用户id
        Long UserId = UserContext.getUser();
        //1.2获取签到时间
        LocalDate now = LocalDate.now();
        //1.3拼接key
        String dateStr = now.format(DateUtils.DATE_SUFFIX_FORMATTER);
        String key = RedisConstans.SIGN_RECORD_KEY_PREFIX + dateStr + UserId;
        //2.查询签到记录
        int dayOfMonth = LocalDate.now().getDayOfMonth();
        List<Long> result = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(
                BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (ObjectUtils.isEmpty(result)) {
            return new Byte[]{};
        }
        int signResult = result.get(0).intValue();
        //3.循环每一天的签到记录，赋值给数组
        Byte[] arr = new Byte[dayOfMonth];
        int index = dayOfMonth - 1;
        while (index >= 0) {
            arr[index--] = (byte) (signResult & 1);
            signResult >>>= 1;

        }
        return arr;
    }
}
